﻿#region Copyright information
// 
// Copyright © 2005-2013 Yongkee Cho. All rights reserved.
// 
// This code is a part of the Biological Object Library and governed under the terms of the
// GNU Lesser General  Public License (LGPL) version 2.1 which accompanies this distribution.
// For more information on the LGPL, please visit http://bol.codeplex.com/license.
// 
// - Filename: LogGaussianDistribution.cs
// - Author: Yongkee Cho
// - Email: yongkeecho@gmail.com
// - Date Created: 2012-09-06 11:39 AM
// - Last Modified: 2013-01-25 3:59 PM
// 
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using BOL.Linq.Descriptive;
using BOL.Maths.Functions;

namespace BOL.Maths.Distributions
{
    /// <summary>
    /// Represents a log Gaussian distribution derived from a normal distribution with mean mu and standard deviation sigma.
    /// <see>ref/LogNormalDistribution</see>
    /// <see>http://en.wikipedia.org/wiki/Lognormal_distribution</see>
    /// </summary>
    public sealed class LogGaussianDistribution : IUnivariateDistribution<double>, IEquatable<LogGaussianDistribution>
    {
        #region Private variables

        private double _mu; // mean
        private double _sigma; // std deviation
        private double _sigmaSq; // sigma square

        #endregion

        #region Public properties

        /// <summary>Gets or sets the mean of a log Gaussian distribution.</summary>
        public double Mu { get { return _mu; } set { _mu = value; } }

        /// <summary>Gets or sets the std deviation of a log Gaussian distribution.</summary>
        public double Sigma { get { return _sigma; } set { _sigma = value; _sigmaSq = value * value;  } }

        /// <summary>Gets the range of a log Gaussian distribution.</summary>
        public IRange<double> Domain { get { return new Range<double>(0, double.PositiveInfinity); } }

        /// <summary>Gets the mean of a log Gaussian distribution.</summary>
        public double Mean { get { return Math.Exp(_mu + _sigmaSq / 2.0); } }

        /// <summary>Gets the median of a log Gaussian distribution.</summary>
        public double Median { get { return Math.Exp(_mu); } }

        /// <summary>Gets the mode of a log Gaussian distribution.</summary>
        public double Mode { get { return Math.Exp(_mu - _sigmaSq); } }

        /// <summary>Gets the variance of a log Gaussian distribution.</summary>
        public double Variance { get { return Math.Exp(2 * _mu + _sigmaSq) * Math.Exp(_sigmaSq - 1); } }

        /// <summary>Gets the skewness of a log Gaussian distribution.</summary>
        public double Skewness { get { return Math.Exp(_sigmaSq + 2) * Math.Sqrt(Math.Exp(_sigmaSq) - 1); } }

        /// <summary>Gets the kurtosis of a log Gaussian distribution.</summary>
        public double Kurtosis { get { return Math.Exp(4 * _sigmaSq) + 2 * Math.Exp(3 * _sigmaSq) + 3 * Math.Exp(2 * _sigmaSq) - 3; } }

        /// <summary>Gets the entropy of a log Gaussian distribution.</summary>
        public double Entropy { get { return .5 + .5 * Math.Log(2 * Math.PI * _sigmaSq) + _mu; } }

        #endregion

        #region Constructor

        /// <summary>
        /// Instantiates a lognormal distribution.
        /// </summary>
        /// <param name="mu">mean</param>
        /// <param name="sigma">stdandard deviation</param>
        public LogGaussianDistribution(double mu, double sigma)
        {
            if (_sigma <= 0)
                throw new ArgumentOutOfRangeException("sigma", "sigma is expected to be positive");

            _mu = mu;
            _sigma = sigma;
            _sigmaSq = sigma * sigma;
        }

        #endregion

        #region ICloneable implementation

        public LogGaussianDistribution Clone()
        {
            return new LogGaussianDistribution(_mu, _sigma);
        }

        object ICloneable.Clone()
        {
            return Clone();
        }
        
        #endregion

        #region Public methods

        /// <summary>
        /// Returns the log Gaussian density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>log Gaussian density</returns>
        public double Pdf(double value)
        {
            if (value < 0)
                throw new ArgumentOutOfRangeException("value", "value is expected to be positive");

            return Math.Exp(-Math.Pow(Math.Log(value) - _mu, 2) / (2 * _sigmaSq)) / (Math.Sqrt(2 * Math.PI) * value * _sigma);
        }

        /// <summary>
        /// Returns the cummulative log Gaussian density given value.
        /// </summary>
        /// <param name="value"></param>
        /// <returns>cummulative log Gaussian density</returns>
        public double Cdf(double value)
        {
            if (value < 0)
                throw new ArgumentOutOfRangeException("value", "value is expected to be positive");

            return value > 0 ? 0.5 * (1 + DistributionFunctions.Erf((Math.Log(value) - _mu) / Math.Sqrt(2) / _sigma)) : 0;
        }

        /// <summary>
        /// Returns the value of the log Gaussian distribution for given probability.
        /// </summary>
        /// <param name="p">probability</param>
        /// <returns></returns>
        public double Quantile(double p)
        {
            return Math.Exp(new GaussianDistribution(_mu, _sigma * _sigma).Quantile(p));
        }

        /// <summary>
        /// Returns a random value from the distribution.
        /// <see>http://en.wikipedia.org/wiki/Log_normal_distribution</see>
        /// </summary>
        /// <returns>random value</returns>
        public double Sample(Random r)
        {
            return Math.Exp(_mu + _sigma * new GaussianDistribution(0, 1).Sample(r));
        }

        /// <summary>
        /// Estimates parameters of the log normal distribution using maximum likelihood.
        /// <see>http://en.wikipedia.org/wiki/Log_normal_distribution#Maximum_likelihood_estimation_of_parameters</see>
        /// </summary>
        public void MaximumLikelihoodEstimate(IEnumerable<double> source)
        {
            if (source == null)
                throw new ArgumentNullException("source");

            var list = source.Select(x => Math.Log(x)).ToList();
            Mu = list.Average();
            Sigma = list.StandardDeviation();
        }

        /// <summary>
        /// Estimates parameters of the log normal distribution using Bayesian methods.
        /// </summary>
        public void BayesianEstimate(IEnumerable<double> source, params dynamic[] priorParameters)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IEquatable<LogGaussianDistribution> implementation

        public bool Equals(LogGaussianDistribution other)
        {
            return _mu.Equals(other._mu) & _sigma.Equals(other._sigma);
        }

        #endregion

        #region Object overriden

        public override int GetHashCode()
        {
            return Mu.GetHashCode() ^ Sigma.GetHashCode();
        }

        public override bool Equals(object other)
        {
            if (other == null)
                throw new ArgumentNullException("other");

            if (!(other is LogGaussianDistribution))
                throw new InvalidCastException("The 'other' argument is not a LogGaussianDistribution object.");
            
            return Equals(other as LogGaussianDistribution);
        }

        public override string ToString()
        {
            return String.Format("LogNormal(mu = {0:G4}, sigma = {1:G4})", _mu, _sigma);
        }

        #endregion
    }
}
